package org.biogroovy.io

import groovy.json.internal.LazyMap
import groovy.util.logging.Slf4j

import org.biogroovy.models.AbsSequence
import org.biogroovy.models.GeneOntology
import org.biogroovy.models.GeneOntologyEvidence
import org.biogroovy.models.GeneOntologyType
import org.biogroovy.models.Pathway

import com.google.common.collect.Multimap


/**
 * This abstract class contains methods used to parse JSON files for sequence-
 * related information.
 * 
 *
 * @param <P> a POJO based on the Sequence.
 */
@Slf4j
public abstract class AbsJsonSeqFetcher<P extends AbsSequence> extends AbsJsonReader<P> {


	/**
	 * This method is responsible for mapping a JSON object into
	 * a POJO object.
	 * @param pojo the pojo.
	 * @param jsonMap the map (or submap) of json nodes
	 */
	@Override
	public void parse(P pojo, Map jsonMap) {
		if (jsonMap == null) {
			throw new RuntimeException("Unable to parse: jsonMap was null")
		}
		parseGO(pojo, jsonMap);
		parseReferences(pojo, jsonMap)
		parseEnsemblIds(pojo, jsonMap)
		parseRefSeqs(pojo, jsonMap)
		parsePathways(pojo, jsonMap)
	}
	

	/**
	 * Parses the references for the sequence
	 * @param gene the sequence object
	 * @param jsonMap the map of properties obtained from the JSON object.
	 */
	protected void parseReferences(P gene, Map jsonMap ) {

		if(jsonMap?.pharmgkb != null) {
			gene.references.put("pharmgkb", jsonMap.pharmgkb)
		}
		
		gene.omimId = jsonMap.'MIM'
		gene.unigeneId =  jsonMap.unigene
		gene.symbol = jsonMap.symbol;
		gene.name = jsonMap.name;
		gene.description = jsonMap.summary;

		jsonMap.alias.each{String syn ->
			gene.synonyms.addAll(syn)
		}
	}

	/**
	 * Parses the pathway information.
	 * @param seq  the sequence object
	 * @param jsonMap a map of properties
	 */
	protected void parsePathways(P seq, Map jsonMap){
		
		Map nodeMap =  [
			'kegg':jsonMap?.'pathway'?.'kegg',
			'reactome':jsonMap?.'pathway'?.'reactome',
			'pid':jsonMap?.'pathway'?.'pid',
			'wikipathways':jsonMap?.'pathway'?.'wikipathways',
			'biocarta':jsonMap?.'pathway'?.'biocarta'
		]

		nodeMap.each{key, value ->
			if (value != null){
				parsePathway(seq.pathways, key, value)
			}
		}
		
		
	}

	/**
	 * Parses a pathway node. 
	 * @param pathwayMap a multimap of pathway information
	 * @param datasource the name of the datasource
	 * @param pathway a pathway node object (which is either a single node, or an arraylist of subnodes)
	 */
	protected void parsePathway(Multimap<String, Pathway> pathwayMap, String datasource, def pathway){
		if (pathway instanceof ArrayList){
			pathway.each{it ->
				pathwayMap.put(datasource, new Pathway(datasource:datasource, name:it.name, accession: it.id))
			}
		}else {
			pathwayMap.put(datasource, new Pathway(datasource:datasource, name:pathway.name, accession: pathway.id))
		}
	}

	/**
	 * This method parses Ensembl IDs.
	 * @param seq the sequence object
	 * @param jsonMap the map of properties from the JSON object
	 */
	protected abstract void parseEnsemblIds(P seq, Map jsonMap)

	/**
	 * This method parses the reference sequence information
	 * @param seq the sequence object
	 * @param jsonMap the map of properties from the JSON object
	 */
	protected abstract void parseRefSeqs(P seq, Map jsonMap)

	/**
	 * Parses the GeneOntology references
	 * @param seq  the sequence object
	 * @param jsonMap a map of properties from the JSON object.
	 */
	protected void parseGO(P seq, Map jsonMap) {

		if (jsonMap?.go?."CC"){
			parseGoNode(seq.goComponentList, GeneOntologyType.COMPONENT, jsonMap.go."CC")
		}

		if(jsonMap?.go?."MF"){
			parseGoNode(seq.goFunctionList, GeneOntologyType.FUNCTION, jsonMap.go."MF")
		}

		if(jsonMap?.go?."BP"){
			parseGoNode(seq.goProcessList, GeneOntologyType.PROCESS, jsonMap.go."BP")
		}
	}

	/**
	 * This method parses a GO node which can either be a list of GO nodes, or a single
	 * node.
	 * @param ontList the list to which the parsed GO object will be added
	 * @param type the type of GO object
	 * @param node the node to be parsed
	 */
	protected void parseGoNode(List<GeneOntology> ontList, GeneOntologyType type, def node){
		if (node instanceof ArrayList){
			node.each {LazyMap it ->
				GeneOntology go = mapGoNode(it, type);
				ontList.add(go)
			}
		}else {
			GeneOntology go = mapGoNode(node, type);
			ontList.add(go)
		}
	}

	/**
	 * This method maps an individual GO node to a GO object.
	 * @param node the GO node
	 * @param type the type of GO object
	 * @return a fully populated GeneOntology object
	 */
	protected GeneOntology mapGoNode(LazyMap node, GeneOntologyType type){
		GeneOntology go = new GeneOntology();
		go.goId = node."id";
		go.name = node."term";
		go.evidence = GeneOntologyEvidence.valueOf(node."evidence");
		go.qualifier = node?."qualifier";
		go.type = type;

		if(node."pubmed" != null){
			go.pmidList.add(node."pubmed");
		}
		return go;
	}
}
